﻿using Newtonsoft.Json.Linq;
using System;
using System.Linq;

namespace Niknak.Social.Twitter
{
    public class Utils
    {
        private const string APIDATEFORMAT = "ddd MMM dd HH:mm:ss %zzzz yyyy";

        internal static IMessage ParseTweetJson<Settings>(JObject tweet)
            where Settings : ISettings, new()
        {
            Settings settings = new Settings();
            IMessage message = settings.Factory.NewMessage();

            try
            {
                DateTime createdAt = DateTime.ParseExact(tweet.Value<string>("created_at"), APIDATEFORMAT, null);

                message.MessageId = tweet.Value<string>("id_str");
                message.CreatedAt = createdAt;
                message.InReplyToUserId = tweet.Property("in_reply_to_user_id_str") != null ? tweet.Value<string>("in_reply_to_user_id_str") : string.Empty;
                message.InReplyToMessageId = tweet.Property("in_reply_to_status_id_str") != null ? tweet.Value<string>("in_reply_to_status_id_str") : string.Empty;
                message.Source = tweet.Value<string>("source");
                message.Text = tweet.Value<string>("text");
                message.RetweetCount = tweet.Property("retweet_count") != null ? tweet.Value<int>("retweet_count") : 0;
                message.FavouriteCount = tweet.Property("favorite_count") != null ? tweet.Value<int>("favorite_count") : 0;

                message.Author = settings.Factory.NewUser();
                message.Author.UserId = tweet["user"].Value<string>("id");
                message.Author.ScreenName = tweet["user"].Value<string>("screen_name");
                message.Author.Name = tweet["user"].Value<string>("name");
                message.Author.ProfileImageUrl = tweet["user"].Value<string>("profile_image_url");

                if (tweet.Property("retweeted_status") != null)
                    message.RetweetedMessage = ParseTweetJson<Settings>((JObject)tweet["retweeted_status"]);

                if (tweet.Property("quoted_status") != null)
                    message.QuotedMessage = ParseTweetJson<Settings>((JObject)tweet["quoted_status"]);

                if (tweet.Property("entities") != null && ((JObject)tweet["entities"]).Property("media") != null)
                    message.Media = tweet["entities"]["media"].Select<JToken, IMedia>(m =>
                    {
                        IMedia media = settings.Factory.NewMediaEntity();

                        media.CreatedAt = createdAt;
                        media.MediaId = m.Value<string>("id_str");
                        media.DisplayUrl = m.Value<string>("display_url");
                        media.ExpandedUrl = m.Value<string>("expanded_url");
                        media.MediaType = m.Value<string>("type");
                        media.MediaUrl = m.Value<string>("media_url");
                        media.MediaUrlHttps = m.Value<string>("media_url_https");
                        media.Url = m.Value<string>("url");
                        media.Indices = m.Value<JArray>("indices").Select(i => (int)i).ToArray();
                        media.Sizes = m["sizes"].Select<JToken, IMediaSize>(s => 
                        {
                            IMediaSize mediaSize = settings.Factory.NewMediaEntitySize();

                            mediaSize.Type = ((JProperty)s).Name;
                            mediaSize.Width = s.First.Value<int>("w");
                            mediaSize.Height = s.First.Value<int>("h");
                            mediaSize.Resize = s.First.Value<string>("resize");

                            return mediaSize;
                        }).ToArray();

                        return media;
                    }).ToArray();

                if (tweet.Property("entities") != null && ((JObject)tweet["entities"]).Property("urls") != null)
                    message.Urls = tweet["entities"]["urls"].Select<JToken, IUrl>(m =>
                    {
                        IUrl url = settings.Factory.NewUrlEntity();

                        url.ShortUrl = m.Value<string>("url");
                        url.DisplayUrl = m.Value<string>("display_url");
                        url.ExpandedUrl = m.Value<string>("expanded_url");
                        url.Indices = m.Value<JArray>("indices").Select(i => (int)i).ToArray();

                        return url;
                    }).ToArray();

                if (tweet.Property("entities") != null && ((JObject)tweet["entities"]).Property("user_mentions") != null)
                    message.UserMentions = tweet["entities"]["user_mentions"].Select<JToken, IUserMention>(m =>
                    {
                        IUserMention userMention = settings.Factory.NewUserMentionEntity();

                        userMention.UserId = m.Value<string>("id_str");
                        userMention.ScreenName = m.Value<string>("screen_name");
                        userMention.Name = m.Value<string>("name");
                        userMention.Indices = m.Value<JArray>("indices").Select(i => (int)i).ToArray();

                        return userMention;
                    }).ToArray();

                if (tweet.Property("entities") != null && ((JObject)tweet["entities"]).Property("hashtags") != null)
                    message.Hashtags = tweet["entities"]["hashtags"].Select<JToken, IHashtag>(m =>
                    {
                        IHashtag hashtag = settings.Factory.NewHashtagEntity();

                        hashtag.Text = m.Value<string>("text");
                        hashtag.Indices = m.Value<JArray>("indices").Select(i => (int)i).ToArray();

                        return hashtag;
                    }).ToArray();

                if (tweet.Property("coordinates") != null && tweet["coordinates"].HasValues && ((JObject)tweet["coordinates"]).Property("coordinates") != null && tweet["coordinates"]["coordinates"].HasValues)
                {
                    JArray coords = (JArray)tweet["coordinates"]["coordinates"];
                    message.Longitude = coords[0].Value<double>();
                    message.Latitude = coords[0].Value<double>();
                }

                message.TextAsHtml = ParseTweetEntitiesToHtml(message);
            }
            catch (Exception ex)
            {
                message = settings.Factory.NewMessage();
                message.Text = "Exception: " + ex.Message;
            }

            return message;
        }


        private static string ParseTweetEntitiesToHtml(IMessage message)
        {
            string text = message.Text;

            //resolve media urls
            if (message.Media != null && message.Media.Any())
                foreach (IMedia ent in message.Media)
                    text = StringReplace(
                        text,
                        ent.Url,
                        string.Format(
                            "<a href=\"{0}\">{1}</a>",
                            ent.Url,
                            ent.DisplayUrl
                        )
                    );

            //resolve urls
            if (message.Urls != null && message.Urls.Any())
                foreach (IUrl ent in message.Urls)
                    text = StringReplace(
                        text,
                        ent.ShortUrl,
                        string.Format(
                            "<a href=\"{0}\">{1}</a>",
                            ent.ShortUrl,
                            string.IsNullOrEmpty(ent.DisplayUrl) ? ent.ShortUrl : ent.DisplayUrl
                        )
                    );

            //resolve user mentions
            if (message.UserMentions != null && message.UserMentions.Any())
                foreach (IUserMention ent in message.UserMentions)
                    text = StringReplace(
                        text,
                        "@" + ent.ScreenName,
                        string.Format(
                            "<a href=\"https://twitter.com/{0}\">@{0}</a>",
                            ent.ScreenName
                        )
                    );

            //resolve hashtags
            if (message.Hashtags != null && message.Hashtags.Any())
                foreach (IHashtag ent in message.Hashtags)
                    text = StringReplace(
                        text,
                        "#" + ent.Text,
                        string.Format(
                            "<a href=\"https://twitter.com/search?q=%23{0}\">#{0}</a>",
                            ent.Text
                        )
                    );

            return text;
        }

        private static string StringReplace(string original, string pattern, string replacement)
        {
            int origStart = original.ToLowerInvariant().IndexOf(pattern.ToLowerInvariant());

            //safety
            if (origStart < 0)
                return original;

            string leftPart = original.Substring(0, origStart);
            string rightPart = original.Substring(origStart + pattern.Length);
            return string.Concat(leftPart, replacement, rightPart);
        }

        internal static bool IsValidTweetJson(JObject tweet)
        {
            if (tweet == null)
                return false;
            if (string.IsNullOrEmpty(tweet.Value<string>("text")))
                return false;
            if (tweet.Property("user") == null || tweet["user"] == null)
                return false;
            if (string.IsNullOrEmpty(tweet["user"].Value<string>("screen_name")))
                return false;
            if (string.IsNullOrEmpty(tweet["user"].Value<string>("name")))
                return false;
            if (string.IsNullOrEmpty(tweet["user"].Value<string>("profile_image_url")))
                return false;

            return true;
        }

        internal static string SpecialEncode(string original)
        {
            string[,] naughtyChars = new string[,]
            {
                { "%", "%25" },
                { "$" , "%24" },
                { "&", "%26" },
                { "+", "%2B" },
                { ",", "%2C" },
                { "/", "%2F" },
                { ":", "%3A" },
                { ";", "%3B" },
                { "=", "%3D" },
                { "?", "%3F" },
                { "@", "%40" },
                { " ", "%20" },
                { "\"" , "%22" },
                { "<", "%3C" },
                { ">", "%3E" },
                { "#", "%23" },
                { "{", "%7B" },
                { "}", "%7D" },
                { "|", "%7C" },
                { "\\", "%5C" },
                { "^", "%5E" },
                { "~", "%7E" },
                { "[", "%5B" },
                { "]", "%5D" },
                { "`", "%60" }
            };

            for (int i = 0; i < naughtyChars.GetUpperBound(0); i++)
                original = original.Replace(naughtyChars[i, 0], naughtyChars[i, 1]);
            return original;
        }
    }
}
